package cn.xw.utils.security.rsa;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Objects;


/**
 * RSA加密解密工具类信息
 *
 * @author Anhui AntLaddie（博客园蚂蚁小哥）
 * @version 1.0
 **/
public class EncryptAndDecryptUtils {

    private static final Logger log = LoggerFactory.getLogger(EncryptAndDecryptUtils.class);

    // 加解密算法关键字，这里只能使用RSA方式解密加密，因为RSA的非对称加密使用最多
    // 因为RSA被广泛用于数字证书、SSL/TLS协议、加密通信、数字签名等领域。
    private static final String KEY_ALGORITHM = "RSA";

    // 证书过期是否可使用（false不使用，true使用）
    private static final Boolean EXPIRED_CERTIFICATE_NOT_USED = false;

    /***
     * 根据密钥库内的私钥对密文进行解析，
     * 说明：当前方法针对类型为.p12的密钥库（证书+私钥），根据密码进入密钥库拿到私钥，并对密文解密
     * @param keystoreStream 密钥库流信息
     * @param keyStoreType 密钥库类型，如：PKCS12、JKS
     * @param keystorePwd 密钥库密码（若密钥库未设置密码则直接传入null）
     * @param privateKeyPwd 密钥库内私钥密码（若密钥库未设置密码则直接传入null）
     * @param privateKeyAlias 密钥库内私钥别名
     * @param ciphertext 密文信息
     * @return 解密后的明文
     */
    public static String keystorePrivateKeyDecrypt(InputStream keystoreStream,
                                            String keyStoreType,
                                            String keystorePwd,
                                            String privateKeyPwd,
                                            String privateKeyAlias,
                                            String ciphertext) {
        // 获取密钥库内的私钥信息（Base64格式）
        String privateKeyBase64Str = BaseParsing.analysisKeystorePrivateKey(keystoreStream,
                keyStoreType, keystorePwd, privateKeyPwd, privateKeyAlias);
        // 解密
        return simplePrivateKeyDecrypt(privateKeyBase64Str, ciphertext);
    }

    /***
     * 根据传入的证书流对象信息，对明文进行加密
     * @param certificateStream 证书流信息
     * @param plaintext 待加密明文
     * @return 加密后的密文字符串
     */
    public static String certificatePublicKeyEncrypt(InputStream certificateStream, String plaintext) {
        // 解析证书并获取证书内的公钥
        CertificateMessage certificateMessage = BaseParsing.certificateParsing(certificateStream);
        // 校验证书
        if (EXPIRED_CERTIFICATE_NOT_USED) {
            // 允许过期证书内的公钥加密
            return simplePublicKeyEncrypt(certificateMessage.getPublicKey(), plaintext);
        } else {
            // 验证是否过期
            boolean overdue = BaseParsing.certificateVerificationTime(certificateMessage);
            if (overdue) {
                throw new RuntimeException("证书过期或未生效，无法使用该证书的公钥信息进行加密！");
            }
            // 使用未生效的证书公钥加密
            return simplePublicKeyEncrypt(certificateMessage.getPublicKey(), plaintext);
        }
    }

    /***
     * 根据传入的证书的Base64的字符串信息，对明文进行加密
     * @param certificateBase64 证书的Base64字符串信息
     * @param plaintext 待加密明文
     * @return 加密后的密文字符串
     */
    public static String certificatePublicKeyEncrypt(String certificateBase64, String plaintext) {
        // 解析证书并获取证书内的公钥
        CertificateMessage certificateMessage = BaseParsing.certificateParsing(certificateBase64);
        // 校验证书
        if (EXPIRED_CERTIFICATE_NOT_USED) {
            // 允许过期证书内的公钥加密
            return simplePublicKeyEncrypt(certificateMessage.getPublicKey(), plaintext);
        } else {
            // 验证是否过期
            boolean overdue = BaseParsing.certificateVerificationTime(certificateMessage);
            if (overdue) {
                throw new RuntimeException("证书过期或未生效，无法使用该证书的公钥信息进行加密。");
            }
            // 使用未生效的证书公钥加密
            return simplePublicKeyEncrypt(certificateMessage.getPublicKey(), plaintext);
        }
    }

    /***
     * 简答加密（根据Base64的公钥字符串进行加密）
     * @param publicKeyBase64Str Base64格式的公钥字符串
     * @param data 待加密的明文数据
     * @return 已加密的信息
     */
    public static String simplePublicKeyEncrypt(String publicKeyBase64Str, String data) {
        // 初始化密钥信息
        RSAPublicKey pubKey = null;
        try {
            // 把公钥信息转换为byte[]类型
            byte[] decoded = Base64.getDecoder().decode(publicKeyBase64Str);
            // 构建公钥信息对象
            pubKey = (RSAPublicKey) KeyFactory.getInstance(KEY_ALGORITHM).
                    generatePublic(new X509EncodedKeySpec(decoded));
        } catch (Exception e) {
            log.info("使用公钥加密失败：{}", e.getMessage());
            throw new RuntimeException(e);
        }
        return cipherEncryptAndDecrypt(pubKey, data);
    }

    /***
     * 简答解密（根据Base64的私钥字符串进行解密）
     * @param privateKeyBase64Str Base64格式的私钥字符串
     * @param base64data 待解密的Base64密文数据
     * @return 已加密的信息
     */
    public static String simplePrivateKeyDecrypt(String privateKeyBase64Str, String base64data) {
        // 初始化密钥信息
        RSAPrivateKey priKey = null;
        try {
            //把私钥信息转换为byte[]类型
            byte[] decoded = Base64.getDecoder().decode(privateKeyBase64Str);
            // 构建私钥信息对象
            priKey = (RSAPrivateKey) KeyFactory.getInstance(KEY_ALGORITHM)
                    .generatePrivate(new PKCS8EncodedKeySpec(decoded));
        } catch (Exception e) {
            log.info("使用私钥解密失败：{}", e.getMessage());
            throw new RuntimeException(e);
        }
        return cipherEncryptAndDecrypt(priKey, base64data);
    }

    /***
     * 加密解密方法，根据密钥解密，根据公钥加密
     * @param key 密钥Key，若是公钥则则加密，私钥解密
     * @param data 需要处理的密文或者明文信息
     * @return 加密解密成功的数据
     */
    private static String cipherEncryptAndDecrypt(Key key, String data) {
        // 初始化返回的数据
        String successData = "";
        // 数据处理（若是密文则Base64解码为byte[]类型，若是明文则转换为byte[]类型数据）
        byte[] dataTextByte = null;

        // 校验参数
        Objects.requireNonNull(key, "加密解密是Key不可为空");
        try {
            // 创建一个指定加密算法名称的Cipher实例，用于执行加密或解密操作
            Cipher cipher = Cipher.getInstance(RSAProperties.ENCRYPTION_ALGORITHM_NAME);
            // 判断当前是公钥还是私钥（公钥就代表方法要加密，私钥就代表方法要解密）
            if (key instanceof PublicKey) {
                // 明文则转换为byte[]类型数据
                dataTextByte = data.getBytes(StandardCharsets.UTF_8);
                // 初始化Cipher实例为加密模式，并设置加密所需的公钥。
                cipher.init(Cipher.ENCRYPT_MODE, key);
                // 加密数据
                byte[] decryptedData = cipher.doFinal(dataTextByte);
                // 转换为Base64格式的数据密文
                successData = Base64.getEncoder().encodeToString(decryptedData);
            } else if (key instanceof PrivateKey) {
                // 密文处理（Base64转换为byte[]）
                dataTextByte = Base64.getDecoder().decode(data.getBytes(StandardCharsets.UTF_8));
                // 初始化Cipher实例为解密模式，并设置解密所需的私钥。
                cipher.init(Cipher.DECRYPT_MODE, key);
                // 解密数据
                byte[] decryptedData = cipher.doFinal(dataTextByte);
                // 字节数组转换为字符串
                successData = new String(decryptedData);
            }
        } catch (Exception e) {
            log.info("加密解密出现问题：{}", e.getMessage());
            throw new RuntimeException(e.getMessage());
        }
        return successData;
    }
}
